package com.che.software.testato.util.jung;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;
import java.io.Serializable;

import org.apache.commons.collections15.Transformer;
import org.apache.log4j.Logger;

import com.che.software.testato.domain.entity.Element;
import com.che.software.testato.domain.entity.Intention;
import com.che.software.testato.domain.entity.MapArrow;
import com.che.software.testato.domain.entity.ProceduralArrow;
import com.che.software.testato.util.ColorUtil;
import com.che.software.testato.util.jung.enumeration.DrawingAlgorithm;

import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout2;
import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.visualization.VisualizationImageServer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

/**
 * Static class used to colorize the JUNG diagrams.
 * 
 * @author Clement HELIOU (clement.heliou@che-software.com).
 * @copyright Che Software.
 * @license GNU General Public License.
 * @see Serializable.
 * @since July, 2011.
 * 
 *        This file is part of Testato.
 * 
 *        Testato is free software: you can redistribute it and/or modify it
 *        under the terms of the GNU General Public License as published by the
 *        Free Software Foundation, either version 3 of the License, or (at your
 *        option) any later version.
 * 
 *        Testato is distributed in the hope that it will be useful, but WITHOUT
 *        ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *        FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *        for more details.
 * 
 *        You should have received a copy of the GNU General Public License
 *        along with Testato. If not, see <http://www.gnu.org/licenses/>.
 * 
 *        Testato's logo is a creation of Arrioch
 *        (http://arrioch.deviantart.com/) and it's distributed under the terms
 *        of the Creative Commons License.
 */
public class DiagramGraphistUtil implements Serializable {

	/**
	 * Constants.
	 */
	private static final double DEFAULT_CHOICE_SHAPE_ROTATION = 0.7854; // Radians.
	private static final double[] DEFAULT_CIRCLE_SIZE = { -5, -20, 80, 80 }, DEFAULT_ELLIPSE_SIZE = { -5, -20, 100, 40 }, DEFAULT_RECTANGLE_SIZE = { -5, -20, 100, 40, 10, 10 };
	private static final int LABEL_OFFSET = 20;
	private static final int[] DEFAULT_DIAMOND_SIZE = { -5, -20, 50, 50 }, DEFAULT_POLYGON_X_POINTS = { -15, 15, 30, 30, 15, -15, -30, -30 }, DEFAULT_POLYGON_Y_POINTS = { 30, 30, 15, -15, -30, -30, -15, 15, 30 };
	private static final Logger LOGGER = Logger.getLogger(DiagramGraphistUtil.class);
	private static final long serialVersionUID = 989685570695060055L;
	private static final String BLUE_HEXA = "92CDDC", DARK_BLUE_HEXA = "17365D", ORANGE_HEXA = "FF9900";
	private static final Stroke DEFAULT_MAP_STROKE = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
	private static final Stroke DEFAULT_PROC_STROKE = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
	private static final Transformer<Element, Font> PROC_ELEMENT_FONT = new Transformer<Element, Font>() {
		@Override
		public Font transform(Element arg0) {
			return ColorUtil.DEFAULT_FONT;
		}
	};
	private static final Transformer<Element, Paint> PROC_BORDER_ELEMENT_PAINT = new Transformer<Element, Paint>() {
		@Override
		public Paint transform(Element element) {
			return ColorUtil.getHSBColor(ColorUtil.DARK_BLUE_COLOR[0], ColorUtil.DARK_BLUE_COLOR[1], ColorUtil.DARK_BLUE_COLOR[2]);
		}
	};
	private static final Transformer<Element, Paint> PROC_ELEMENT_PAINT = new Transformer<Element, Paint>() {
		@Override
		public Paint transform(Element element) {
			return ColorUtil.getHSBColor(ColorUtil.GREY_COLOR[0], ColorUtil.GREY_COLOR[1], ColorUtil.GREY_COLOR[2]);
		}
	};
	private static final Transformer<Element, Shape> PROC_ELEMENT_SHAPE = new Transformer<Element, Shape>() {
		@Override
		public Shape transform(Element element) {
			if (0 != element.getActivityId()) {
				return new RoundRectangle2D.Double(DEFAULT_RECTANGLE_SIZE[0], DEFAULT_RECTANGLE_SIZE[1], DEFAULT_RECTANGLE_SIZE[2], DEFAULT_RECTANGLE_SIZE[3], DEFAULT_RECTANGLE_SIZE[4], DEFAULT_RECTANGLE_SIZE[5]);
			}
			switch (element.getPointType()) {
			case CHOICE:
				AffineTransform at = new AffineTransform();
				at.rotate(DEFAULT_CHOICE_SHAPE_ROTATION);
				return at.createTransformedShape(new Rectangle(DEFAULT_DIAMOND_SIZE[0], DEFAULT_DIAMOND_SIZE[1], DEFAULT_DIAMOND_SIZE[2], DEFAULT_DIAMOND_SIZE[3]));
			case JOIN:
				return new Polygon(DEFAULT_POLYGON_X_POINTS, DEFAULT_POLYGON_Y_POINTS, DEFAULT_POLYGON_X_POINTS.length);
			case NEGATIVE_END:
			case POSITIVE_END:
			case START:
				return new Ellipse2D.Double(DEFAULT_CIRCLE_SIZE[0], DEFAULT_CIRCLE_SIZE[1], DEFAULT_CIRCLE_SIZE[2], DEFAULT_CIRCLE_SIZE[3]);
			default:
				return null; // Doesn't happened.
			}
		}
	};
	private static final Transformer<Element, String> PROC_ELEMENT_LABEL = new Transformer<Element, String>() {
		@Override
		public String transform(Element element) {
			return "<html><font color=\"#" + DARK_BLUE_HEXA + "\">" + element + "</font></html>";
		}
	};
	private static final Transformer<Element, Stroke> PROC_ELEMENT_STROKE = new Transformer<Element, Stroke>() {
		@Override
		public Stroke transform(Element arg0) {
			return DEFAULT_PROC_STROKE;
		}
	};
	private static final Transformer<Intention, Font> MAP_NODE_FONT = new Transformer<Intention, Font>() {
		@Override
		public Font transform(Intention arg0) {
			return ColorUtil.DEFAULT_FONT;
		}
	};
	private static final Transformer<Intention, Paint> MAP_NODE_PAINT = new Transformer<Intention, Paint>() {
		@Override
		public Paint transform(Intention intention) {
			return ColorUtil.getHSBColor(ColorUtil.GREY_COLOR[0], ColorUtil.GREY_COLOR[1], ColorUtil.GREY_COLOR[2]);
		}
	};
	private static final Transformer<Intention, Paint> MAP_BORDER_NODE_PAINT = new Transformer<Intention, Paint>() {
		@Override
		public Paint transform(Intention intention) {
			return ColorUtil.getHSBColor(ColorUtil.DARK_BLUE_COLOR[0], ColorUtil.DARK_BLUE_COLOR[1], ColorUtil.DARK_BLUE_COLOR[2]);
		}
	};
	private static final Transformer<Intention, Shape> MAP_NODE_SHAPE = new Transformer<Intention, Shape>() {
		@Override
		public Shape transform(Intention intention) {
			return new Ellipse2D.Double(DEFAULT_ELLIPSE_SIZE[0], DEFAULT_ELLIPSE_SIZE[1], DEFAULT_ELLIPSE_SIZE[2], DEFAULT_ELLIPSE_SIZE[3]);
		}
	};
	private static final Transformer<Intention, String> MAP_NODE_LABEL = new Transformer<Intention, String>() {
		@Override
		public String transform(Intention intention) {
			return "<html><font color=\"#" + DARK_BLUE_HEXA + "\">" + intention + "</font></html>";
		}
	};
	private static final Transformer<Intention, Stroke> MAP_NODE_STROKE = new Transformer<Intention, Stroke>() {
		@Override
		public Stroke transform(Intention intention) {
			return DEFAULT_MAP_STROKE;
		}
	};
	private static final Transformer<MapArrow, Font> MAP_ARROW_FONT = new Transformer<MapArrow, Font>() {
		@Override
		public Font transform(MapArrow arrow) {
			return ColorUtil.DEFAULT_FONT;
		}
	};
	private static final Transformer<MapArrow, Paint> MAP_ARROW_PAINT = new Transformer<MapArrow, Paint>() {
		@Override
		public Paint transform(MapArrow arrow) {
			return (arrow.isOperationalized()) ? ColorUtil.getHSBColor(ColorUtil.ORANGE_COLOR[0], ColorUtil.ORANGE_COLOR[1], ColorUtil.ORANGE_COLOR[2]) : (arrow.isRefined()) ? ColorUtil.getHSBColor(ColorUtil.BLUE_COLOR[0], ColorUtil.BLUE_COLOR[1], ColorUtil.BLUE_COLOR[2]) : ColorUtil.getHSBColor(ColorUtil.DARK_BLUE_COLOR[0], ColorUtil.DARK_BLUE_COLOR[1], ColorUtil.DARK_BLUE_COLOR[2]);
		}
	};
	private static final Transformer<MapArrow, String> MAP_ARROW_LABEL = new Transformer<MapArrow, String>() {
		@Override
		public String transform(MapArrow arrow) {
			return "<html><font color=\"#" + ((arrow.isOperationalized()) ? ORANGE_HEXA : (arrow.isRefined()) ? BLUE_HEXA : DARK_BLUE_HEXA) + "\">" + arrow + "</font></html>";
		}
	};
	private static final Transformer<MapArrow, Stroke> MAP_ARROW_STROKE = new Transformer<MapArrow, Stroke>() {
		@Override
		public Stroke transform(MapArrow arrow) {
			return DEFAULT_MAP_STROKE;
		}
	};
	private static final Transformer<ProceduralArrow, Font> PROC_ARROW_FONT = new Transformer<ProceduralArrow, Font>() {
		@Override
		public Font transform(ProceduralArrow arrow) {
			return ColorUtil.DEFAULT_FONT;
		}
	};
	private static final Transformer<ProceduralArrow, Paint> PROC_ARROW_PAINT = new Transformer<ProceduralArrow, Paint>() {
		@Override
		public Paint transform(ProceduralArrow arrow) {
			return ColorUtil.getHSBColor(ColorUtil.DARK_BLUE_COLOR[0], ColorUtil.DARK_BLUE_COLOR[1], ColorUtil.DARK_BLUE_COLOR[2]);
		}
	};
	private static final Transformer<ProceduralArrow, String> PROC_ARROW_LABEL = new Transformer<ProceduralArrow, String>() {
		@Override
		public String transform(ProceduralArrow arrow) {
			return "<html><font color=\"#" + ((null != arrow.getLabel() && !"".equals(arrow.getLabel())) ? BLUE_HEXA : DARK_BLUE_HEXA) + "\">" + arrow + "</font></html>";
		}
	};
	private static final Transformer<ProceduralArrow, Stroke> PROC_ARROW_STROKE = new Transformer<ProceduralArrow, Stroke>() {
		@Override
		public Stroke transform(ProceduralArrow arrow) {
			return DEFAULT_PROC_STROKE;
		}
	};
	private static final Transformer<Context<Graph<Intention, MapArrow>, MapArrow>, Shape> MAP_ARROW_SHAPE = new Transformer<Context<Graph<Intention, MapArrow>, MapArrow>, Shape>() {
		@Override
		public Shape transform(Context<Graph<Intention, MapArrow>, MapArrow> context) {
			return new EdgeShape.QuadCurve<Intention, MapArrow>().transform(context);
		}
	};
	private static final Transformer<Context<Graph<Element, ProceduralArrow>, ProceduralArrow>, Shape> PROC_ARROW_SHAPE = new Transformer<Context<Graph<Element, ProceduralArrow>, ProceduralArrow>, Shape>() {
		@Override
		public Shape transform(Context<Graph<Element, ProceduralArrow>, ProceduralArrow> context) {
			return new EdgeShape.BentLine<Element, ProceduralArrow>().transform(context);
		}
	};

	/**
	 * Private default builder; the class can't be instancied.
	 */
	private DiagramGraphistUtil() {
	}

	/**
	 * Returns the object containing the colorized version of a map diagram.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram to colorized.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	public static VisualizationImageServer<Intention, MapArrow> getColorizedMapDiagram(DirectedGraph<Intention, MapArrow> mapDiagram) {
		LOGGER.debug("getColorizedMapDiagram(" + mapDiagram + ").");
		return DiagramGraphistUtil.getColorizedMapDiagramFromAlgorithmAndSize(mapDiagram, DrawingAlgorithm.KK, ColorUtil.REDUCE_SIZE);
	}

	/**
	 * Returns the object containing the colorized version of a map diagram with
	 * given algorithm and size.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram to colorized.
	 * @param algorithm the algorithm to used.
	 * @param imageSize the desired image size.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	public static VisualizationImageServer<Intention, MapArrow> getColorizedMapDiagramFromAlgorithmAndSize(DirectedGraph<Intention, MapArrow> mapDiagram, DrawingAlgorithm algorithm, Dimension imageSize) {
		LOGGER.debug("getColorizedMapDiagramFromAlgorithmAndSize(" + mapDiagram + "," + algorithm + ").");
		Layout<Intention, MapArrow> layout = DiagramGraphistUtil.getMapLayoutFromAlgorithmAndSize(mapDiagram, algorithm, imageSize);
		VisualizationImageServer<Intention, MapArrow> vis = new VisualizationImageServer<Intention, MapArrow>(layout, imageSize);
		vis.getRenderContext().setArrowDrawPaintTransformer(MAP_ARROW_PAINT);
		vis.getRenderContext().setEdgeArrowStrokeTransformer(MAP_ARROW_STROKE);
		vis.getRenderContext().setEdgeDrawPaintTransformer(MAP_ARROW_PAINT);
		vis.getRenderContext().setEdgeFontTransformer(MAP_ARROW_FONT);
		vis.getRenderContext().setEdgeLabelTransformer(MAP_ARROW_LABEL);
		vis.getRenderContext().setEdgeShapeTransformer(MAP_ARROW_SHAPE);
		vis.getRenderContext().setEdgeStrokeTransformer(MAP_ARROW_STROKE);
		vis.getRenderContext().setLabelOffset(LABEL_OFFSET);
		vis.getRenderContext().setVertexDrawPaintTransformer(MAP_BORDER_NODE_PAINT);
		vis.getRenderContext().setVertexFillPaintTransformer(MAP_NODE_PAINT);
		vis.getRenderContext().setVertexFontTransformer(MAP_NODE_FONT);
		vis.getRenderContext().setVertexLabelTransformer(MAP_NODE_LABEL);
		vis.getRenderContext().setVertexShapeTransformer(MAP_NODE_SHAPE);
		vis.getRenderContext().setVertexStrokeTransformer(MAP_NODE_STROKE);
		vis.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
		vis.setBackground(Color.WHITE);
		vis.validate();
		return vis;
	}

	/**
	 * Returns the object containing the colorized version of a procedural
	 * diagram.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param proceduralDiagram the procedural diagram to colorized.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	public static VisualizationImageServer<Element, ProceduralArrow> getColorizedProceduralDiagram(DirectedGraph<Element, ProceduralArrow> proceduralDiagram) {
		LOGGER.debug("getColorizedProceduralDiagram(" + proceduralDiagram + ").");
		return DiagramGraphistUtil.getColorizedProceduralDiagramFromAlgorithmAndSize(proceduralDiagram, DrawingAlgorithm.ISOM, ColorUtil.REDUCE_SIZE);
	}

	/**
	 * Returns the object containing the colorized version of a procedural
	 * diagram with given algorithm and size.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param proceduralDiagram the procedural diagram to colorized.
	 * @param algorithm the algorithm to used.
	 * @param imageSize the desired image size.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	public static VisualizationImageServer<Element, ProceduralArrow> getColorizedProceduralDiagramFromAlgorithmAndSize(DirectedGraph<Element, ProceduralArrow> proceduralDiagram, DrawingAlgorithm algorithm, Dimension imageSize) {
		LOGGER.debug("getColorizedProceduralDiagramFromAlgorithmAndSize(" + proceduralDiagram + "," + algorithm + "," + imageSize.getHeight() + "," + imageSize.getWidth() + ").");
		Layout<Element, ProceduralArrow> layout = DiagramGraphistUtil.getProceduralLayoutFromAlgorithmAndSize(proceduralDiagram, algorithm, imageSize);
		VisualizationImageServer<Element, ProceduralArrow> vis = new VisualizationImageServer<Element, ProceduralArrow>(layout, imageSize);
		vis.getRenderContext().setArrowDrawPaintTransformer(PROC_ARROW_PAINT);
		vis.getRenderContext().setArrowFillPaintTransformer(PROC_ARROW_PAINT);
		vis.getRenderContext().setEdgeArrowStrokeTransformer(PROC_ARROW_STROKE);
		vis.getRenderContext().setEdgeDrawPaintTransformer(PROC_ARROW_PAINT);
		vis.getRenderContext().setEdgeFontTransformer(PROC_ARROW_FONT);
		vis.getRenderContext().setEdgeLabelTransformer(PROC_ARROW_LABEL);
		vis.getRenderContext().setEdgeShapeTransformer(PROC_ARROW_SHAPE);
		vis.getRenderContext().setEdgeStrokeTransformer(PROC_ARROW_STROKE);
		vis.getRenderContext().setLabelOffset(LABEL_OFFSET);
		vis.getRenderContext().setVertexDrawPaintTransformer(PROC_BORDER_ELEMENT_PAINT);
		vis.getRenderContext().setVertexFillPaintTransformer(PROC_ELEMENT_PAINT);
		vis.getRenderContext().setVertexFontTransformer(PROC_ELEMENT_FONT);
		vis.getRenderContext().setVertexLabelTransformer(PROC_ELEMENT_LABEL);
		vis.getRenderContext().setVertexShapeTransformer(PROC_ELEMENT_SHAPE);
		vis.getRenderContext().setVertexStrokeTransformer(PROC_ELEMENT_STROKE);
		vis.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
		vis.setBackground(Color.WHITE);
		vis.validate();
		return vis;
	}

	/**
	 * Return the layout corresponding to the parameters.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram to put into the layout.
	 * @param algorithm the desired algorithm.
	 * @param imageSize the desired image size.
	 * @return the resulting layout object.
	 * @since July, 2011.
	 */
	private static Layout<Intention, MapArrow> getMapLayoutFromAlgorithmAndSize(DirectedGraph<Intention, MapArrow> mapDiagram, DrawingAlgorithm algorithm, Dimension imageSize) {
		LOGGER.debug("getMapLayoutFromAlgorithmAndSize(" + algorithm + "," + imageSize.getHeight() + "," + imageSize.getWidth() + ").");
		Layout<Intention, MapArrow> layout = null;
		switch (algorithm) {
		case CIRCLE:
			layout = new CircleLayout<Intention, MapArrow>(mapDiagram);
			break;
		case FR:
			layout = new FRLayout<Intention, MapArrow>(mapDiagram);
			break;
		case FR2:
			layout = new FRLayout2<Intention, MapArrow>(mapDiagram);
			break;
		case ISOM:
			layout = new ISOMLayout<Intention, MapArrow>(mapDiagram);
			break;
		case KK:
			layout = new KKLayout<Intention, MapArrow>(mapDiagram);
			break;
		case SPRING:
			layout = new SpringLayout<Intention, MapArrow>(mapDiagram);
			break;
		case SPRING2:
			layout = new SpringLayout2<Intention, MapArrow>(mapDiagram);
			break;
		default: // Doesn't happened.
			break;
		}
		layout.setSize(imageSize);
		return layout;
	}

	/**
	 * Return the layout corresponding to the parameters.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param proceduralDiagram the procedural diagram to put into the layout.
	 * @param algorithm the desired algorithm.
	 * @param imageSize the desired image size.
	 * @return the resulting layout object.
	 * @since July, 2011.
	 */
	private static Layout<Element, ProceduralArrow> getProceduralLayoutFromAlgorithmAndSize(DirectedGraph<Element, ProceduralArrow> proceduralDiagram, DrawingAlgorithm algorithm, Dimension imageSize) {
		LOGGER.debug("getProceduralLayoutFromAlgorithmAndSize(" + proceduralDiagram + "," + algorithm + "," + imageSize.getHeight() + "," + imageSize.getWidth() + ").");
		Layout<Element, ProceduralArrow> layout = null;
		switch (algorithm) {
		case CIRCLE:
			layout = new CircleLayout<Element, ProceduralArrow>(proceduralDiagram);
			break;
		case FR:
			layout = new FRLayout<Element, ProceduralArrow>(proceduralDiagram);
			break;
		case FR2:
			layout = new FRLayout2<Element, ProceduralArrow>(proceduralDiagram);
			break;
		case ISOM:
			layout = new ISOMLayout<Element, ProceduralArrow>(proceduralDiagram);
			break;
		case KK:
			layout = new KKLayout<Element, ProceduralArrow>(proceduralDiagram);
			break;
		case SPRING:
			layout = new SpringLayout<Element, ProceduralArrow>(proceduralDiagram);
			break;
		case SPRING2:
			layout = new SpringLayout2<Element, ProceduralArrow>(proceduralDiagram);
			break;
		default: // Doesn't happened.
			break;

		}
		layout.setSize(imageSize);
		return layout;
	}
}